Учебник языка Перфо для школьников — различия между версиями

Материал из ТХАБ.РФ
Перейти к: навигация, поиск
м (Введение в синтаксис)
м (Упражнение 6)
 
(не показаны 103 промежуточные версии 4 участников)
Строка 3: Строка 3:
 
* https://ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников - Оригинал "Введение в Scheme для школьников"  
 
* https://ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников - Оригинал "Введение в Scheme для школьников"  
 
* https://habr.com/ru/post/505928/ Почему функциональное программирование такое сложное - Хабр
 
* https://habr.com/ru/post/505928/ Почему функциональное программирование такое сложное - Хабр
* Часть викиучебника «[[Лисп]]»
+
 
 
* Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]].
 
* Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]].
  
Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[Перфо]], который является русским вариантом [[w:Scheme|Scheme]] (''Ским'').
+
Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[Перфо]], который является русским вариантом [[w:Scheme|Scheme]] (''Ским''). [[Перфо]] не является копией Ским. Это адаптированный для практического программирования вариант интерпретатора функционального языка программирования.
 +
 
 +
== Примерное соответствие ключевых слов Ским (Scheme) и [[Перфо]] ==
 +
* ; - // - комментарий, многострочный комментарий /*  */ - не поддерживается
 +
* define - перем, функция - (определить)
 +
** перем - создаёт и изменяет переменную
 +
** функция - создаёт функцию
 +
** lambda - функ - создаёт анонимную (лямбда)-функцию
 +
* read - ввод - ввод строки пользователя
 +
* display - вывод
 +
* write - вывод
 +
* newline - ПС - перевод строки
 +
* if - если - оператор ветвления ЕСЛИ
 +
* positive? - (> переменная 0)
 +
* string-append -  - склеивание строк
 +
* set! -
 +
* list - список
 +
* null? - Пустой? - для использования необходимо подключить [[Стандартную Библиотеку Перфо]]
 +
* map - [[ПоВсем]]
 +
* apply - [[ПоКаждому]]
 +
* begin -  не к спискам относится, а к последовательности действий... в Перфо можно просто писать последовательность без ключевого слова '''begin'''. Например, на Scheme надо было бы написать:
 +
 
 +
(+ 3 (begin (set! y 4) y)  5)
 +
 
 +
а на Перфо будет так:
 +
 
 +
(+ 3 ((перем ф 4) ф) 5)
 +
 
 +
Перем и Уст с переменными работают похоже, пока дело не касается установки значений полей или свойств объектов, тут уже только Уст подойдет...
  
== Соответствие ключевых слов Ским и Перфо ==
+
=== [[Перевод терминов функционального программирования]] ===
* define - перем, функция
 
  
 
== Введение в синтаксис==
 
== Введение в синтаксис==
Строка 40: Строка 67:
  
 
  (Перем имя <первоначальное значение>)
 
  (Перем имя <первоначальное значение>)
 
Пример:
 
  
 
Пример:
 
Пример:
Строка 47: Строка 72:
 
  (перем ширина 3)
 
  (перем ширина 3)
 
  (перем высота 7)
 
  (перем высота 7)
(* 2 (+ ширина высота))  
+
(* 2 (+ ширина высота))  
  
Прочитаем записанное по-русски: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20.
+
Прочитаем записанное по-человечески: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20.
  
 
Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так:
 
Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так:
Строка 55: Строка 80:
 
  (перем А 3)
 
  (перем А 3)
 
  (перем Б 4)
 
  (перем Б 4)
(+ (* А А) (* Б Б))  
+
(+ (* А А) (* Б Б))  
  
 
Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Перфо — <code>квадрат</code>:
 
Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Перфо — <code>квадрат</code>:
  
 
  (+ (квадрат А) (квадрат Б))  
 
  (+ (квадрат А) (квадрат Б))  
 +
 +
=== Добавим новые слова в программу ===
  
 
«Сумма квадрата <code>А</code> и квадрата <code>Б</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>квадрат</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом:
 
«Сумма квадрата <code>А</code> и квадрата <code>Б</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>квадрат</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом:
Строка 67: Строка 94:
 
Общий формат:
 
Общий формат:
  
  (функция (название параметр параметр …) тело_функции)  
+
  (функция (название параметр параметр …) (тело_функции))  
  
Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>square2</code>:
+
Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>квадрат2</code>:
  
 
  (функция (квадрат2 x) (* 2 2) (* x x))  
 
  (функция (квадрат2 x) (* 2 2) (* x x))  
Строка 86: Строка 113:
 
Перфо предоставляет нам несколько готовых «глаголов»:
 
Перфо предоставляет нам несколько готовых «глаголов»:
  
; <code>ввод</code>: для чтения имени,
+
* <code>ввод</code>: для чтения имени,
; <code>вывод</code>: вывод чего-то на дисплее,
+
* <code>вывод</code>: вывод чего-то на дисплее,
; <code>ПС</code>: вывод перевода строки.
+
* <code>ПС</code>: вывод перевода строки.
  
 
Мы бы хотели иметь такие «глаголы»:
 
Мы бы хотели иметь такие «глаголы»:
  
; <code>привет</code>: для приветствия с одним параметром — именем пользователя;
+
* <code>привет</code>: для приветствия с одним параметром — именем пользователя;
; <code>пользователь</code>: для получения имени пользователя, без параметров.
+
* <code>пользователь</code>: для получения имени пользователя, без параметров.
  
 
Наша задача выглядела бы так:
 
Наша задача выглядела бы так:
Строка 101: Строка 128:
 
Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы.
 
Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы.
  
  (define (привет имя)
+
  (функция (привет имя) // объявление функции Привет с параметром Имя
  (display "Привет, ")
+
  (вывод "Привет, ")
  (display имя)
+
  (вывод имя)
  (display "!")
+
  (вывод "!")
  (newline))
+
  (ПС))
(define (пользователь)
+
(функция (пользователь) // объявление функции Пользователь без параметров
  (write "Представьтесь:")
+
  (ввод "Представьтесь: "))
  (read))
+
(привет (пользователь))  
(привет (пользователь))  
+
 
 +
[[Перфо]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется.
  
[[w:Лисп|Лисп]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется.
+
=== Ввод данных в программу ===
 +
 
 +
Ввод данных пользователя прост и логичен. Как мы объявляем переменную ?
 +
 
 +
(Перем ИмяПользователя "Володя")
 +
(Вывод "Вы ввели имя " ИмяПользователя)
 +
 
 +
Как присвоить новое значение переменной '''ИмяПользователя''' ? В Функцию присваивания '''Перем''' вместо "Володя" необходимо вставить результат работы другой функции.
 +
 
 +
(Перем ИмяПользователя (Ввод "Введи своё имя: "))
 +
(Вывод "Вас зовут " ИмяПользователя ПС) // проверим какое  значение присвоилось, напоминаю ПС - перевод строки
 +
 
 +
Формат функции '''Ввод'''
 +
 
 +
(ввод "Сообщение-подсказка пользователю, что ввести ")
 +
 
 +
=== Операция унарный минус ===
 +
(-) "минус" - Перфо тоже функция/глагол. Если её применить к значению (- 3) или (- ЛюбоеЧисло) то знак поменяется на противоположный. Математическим аналогом является умножение на -1. Операция унарный плюс возможна но не имеет смысла как умножение на +1.  
 +
 
 +
(Вывод "Отрицательное число -3 = " -3 ПС)
 +
(Вывод "Операция унарный минус (- 3) = " (- 3) ПС)
 +
(Вывод "Операция унарный минус (- -3) = " (- -3) ПС)
 +
 
 +
=== Ветвление программы - оператор '''если''' ===
 
Например, функцию «модуль числа» можно определить так:
 
Например, функцию «модуль числа» можно определить так:
  
  (define (abs x)
+
  (функция (модуль ф) // описание функции Модуль с параметром Ф
  (if (positive? x )
+
          (если (> ф 0) // Если ф>0 то выполняется Часть 1 иначе Часть 2
      x
+
                  ф // Часть 1
      (- x)))  
+
                (- ф) // Часть 2
 +
          ) // Конец алгоритма функции Модуль
 +
  ) // Конец описания функции Модуль
  
«Определим, что функция <code>abs</code> возвращает свой аргумент, если он положителен, иначе — <code>-x</code>». А можно и так:
+
«Определим, что функция <code>модуль</code> возвращает свой аргумент, если он положителен, иначе — <code>-ф</code>». А можно и так:
  
  (define (abs x)
+
  (функция (модуль ф) // описание функции Модуль с параметром Ф
  ((if (positive? x) + -) x))  
+
    // результат выполнения функции - знак (+) или (-) который применяется к переменной Ф
 +
          ((если (> ф 0) + -) ф) // Если ф>0 то возвращает знак (+) иначе возвращает знак (-)
 +
          // знаки + и - на самом деле функции которые применяются к переменным или числам
 +
          // поэтому результат выполнения функции Модуль - одна из 2-х функций + или -
 +
          // в  зависимости от значения Ф
 +
)
  
«…если аргумент положителен, то плюс, иначе минус <code>x</code>». Здесь в результате исполнения выражения <code>if</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>x</code>. Полагаю, что смысл конструкции <code>if</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков:
+
«…если аргумент положителен, то плюс, иначе минус <code>ф</code>». Здесь в результате исполнения выражения <code>если</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>ф</code>. Полагаю, что смысл конструкции <code>если</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков:
  
  (if условие <действие, если условие выполняется> <действие в противном случае>)
+
  (если условие <действие, если условие выполняется> <действие в противном случае>)
  
 
== Где посмотреть и попробовать ==
 
== Где посмотреть и попробовать ==
  
В теории всё хорошо, а где немного попрактиковаться? В мире можно найти много прекрасно разработанных сред для работы со Scheme. К сожалению, большинство документации по Scheme на английском языке, но можно найти и отличные введения на русском — язык-то простой.
+
В теории всё хорошо, а где немного попрактиковаться? Необходимо скачать среду языка [[Перфолента.NET]] 0.46+, в ней выбрать Файл - "Создать программа на языке Перфо..."
  
== Кот в мешке ==
+
== Рекурсия и итерация ==
  
Простота Scheme обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии.
+
Простота Перфо ([[Лисп]], [[Scheme]] ) обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии.
  
 
=== Упражнение ===
 
=== Упражнение ===
  
Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] <math>f(n) = 1 \cdot 2 \cdots n</math>. Одна из них основана на [[w:Рекурсия|рекурсии]], а другая – на [[w:Итерация|итерациях]]. Напишите на Scheme рекурсивную и основанную на итерациях реализации функции возведения в степень <math>f(a, n) = a^n</math>.
+
Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] (т.е. умножаются все числа от 1 до N)
 +
 
 +
f(N) = 1 * 2 * N
 +
 
 +
Одна из них основана на [[w:Рекурсия|рекурсии]] - это когда функция (или кусок кода) вызывает сама себя, а другая – на [[w:Итерация|итерациях]] - это когда кусок кода повторяется несколько раз в цикле.
 +
 
 +
Напишите на Перфо рекурсивную (вызывающую саму себя) и основанную на итерациях (повторении) реализации функции возведения в степень N числа А
 +
 
 +
f(А, N) = A^N.
 +
 
 +
==== Вариант 1. Рекурсия - функция вызывает сама себя ====
  
==== Вариант 1 ====
+
(функция (факториал ф) // функция Факториал с параметром Ф
 +
          (если (= ф 0) // если a = 0
 +
                  1 // то функция возвращает в то место в коде откуда её вызвали 1
 +
                (* ф (факториал (- ф 1) ) ) // умножается значение Ф на значение возвращаемое
 +
                  // (факториал (- ф 1) ) - с параметром Ф-1 (на единицу меньше)
 +
                  // т.е. внутри функции '''факториал''' вызывается функция '''факториал'''
 +
                  // с другим (на единицу меньше) параметром!!
 +
    )
 +
)
  
(define (factorial n)
+
==== Вариант 2. Повторение (Итерация) ====
  (if (= n 0)
+
Вычисление факториала методом повторения (итерации):
      1
 
      (* n (factorial (- n 1)))))
 
  
==== Вариант 2 ====
+
  (функция (факториал-повторением результат счётчик) // определение функции Факториал-повторением
  (define (fact-iter result counter)
+
        (если (= счётчик 0)
  (if (= counter 0)
+
              результат
    result
+
              (факториал-повторением (* счётчик результат)
    (fact-iter (* counter result)
+
                                  (- счётчик 1))))
                (- counter 1))))
+
  (функция (факториал Число) (факториал-повторением 1 Число)) // описание функции Факториал которая использует функцию факториал-повторением
  (define (factorial n) (fact-iter 1 n))
+
// использование функции Факториал
 +
(Вывод "Факториал 6: " (факториал 6)) // вывод " Факториал 6:" +  результат работы функции "Факториал" с параметром 6
 +
                                      //(которая сама вызывает другую функцию факториал-повторением )
  
 
== Повторение – мать учения ==
 
== Повторение – мать учения ==
  
Приведём небольшую шпаргалку по базовым конструкциям Scheme для тех, кто еще не ознакомился с первой частью статьи.
+
Приведём небольшую шпаргалку по базовым конструкциям [[Перфо]].
  
 
Базовый синтаксис:
 
Базовый синтаксис:
Строка 167: Строка 243:
 
  (+ 1 2 3 4)                    // сумма первых 4-х натуральных чисел
 
  (+ 1 2 3 4)                    // сумма первых 4-х натуральных чисел
 
  (+ (+ 1 2) 5 6)                // возможно вкладывать одни вызовы функций в другие
 
  (+ (+ 1 2) 5 6)                // возможно вкладывать одни вызовы функций в другие
  (string-append "hello" "world") // результат — склеенная строка "helloworld"  
+
  (+ "Привет" "Мир")             // результат — склеенная строка "ПриветМир"  
  
Задание переменных и функций:
+
Присвоить значение переменной:
  
  (define <имя> <значение>)
+
  (перем <имя> <значение>)  
  (define (<имя> <аргумент> …) <тело функции>)
+
 
// функция возвращает значение последнего вычислительного выражения в теле функции  
+
Объявление Функции:
 +
 
 +
  (функция (<имя> <параметр1> … <параметрН>) <тело функции>)
 +
 
 +
Функция возвращает значение последнего вычисленного выражения в теле функции  
  
 
Например:
 
Например:
  
  (define a 3)
+
  (перем А 3) // создаётся переменная А = 3
  (define b 4)
+
  (перем Б 4) // создаётся переменная Б = 4
  (define (square x) (* x x)) ; вычисляет квадрат числа
+
  (функция (квадрат П) (* П П)) // описывается функция которая вычисляет квадрат числа переданного в параметр П
  (define (+a x) (+ x a)) ; да-да, можно давать и такие имена функциям  
+
// результат = 16
 +
  (функция (+А Б) (+ Б А)) // да-да, можно давать и такие имена функциям (любое символьное выражение)
 +
// результат = 7
  
Дополнительные конструкции:
+
Конструкция Если То Иначе:
  
  (if <условие> <действие при успехе условия> <действие при неудаче>)
+
  (если <условие> <действие при успехе условия> <действие при неудаче>)
  (begin <первое действие> <второе действие> …)  
+
 
 +
Выполнение действий последовательно, как  в обычном языке программирования:
 +
 
 +
  (<первое действие> <второе действие> …)  
  
 
Пример:
 
Пример:
  (if (> a 0)
+
  (Перем Б -10) // Переменной Б присваивается 10
     (display "a > 0")) ; действие при неудаче можно не указывать
+
(если (> Б 0) // Если Б>0
  (begin (display 1)
+
     (вывод "Б > 0" ПС)) // Б > 0 - ПРАВДА выводится "Б > 0" и переводится строка, 
      (display 2)
+
    // действие Б > 0 - ЛОЖЬ -  можно не указывать
      (display 3)) ; последовательно выполнятся действия, и на экран будет выведено: 123
+
  ((вывод 1)
 +
  (вывод 2 ПС)
 +
  (вывод 3)) // последовательно выполнятся действия, и на экран будет выведено: 12 и 3
 +
 
 +
Если вы ранее использовали Scheme то в нём последовательность действий (<первое действие> <второе действие> …) записывается со словом begin ('''begin''' <первое действие> <второе действие> …)
  
== И опять про повторение: функция <code>repeat</code> ==
+
== И опять про повторение: функция <code>повторить</code> ==
  
Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие один раз, то вы просто его делаете один раз:
+
Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие 1 раз, то вы просто его делаете 1 раз:
  
  (display "hello")  
+
  (Вывод "Привет")  
  
Если вам надо выполнить какое-то действие два раза, то вы просто повторяете его:
+
Если вам надо выполнить какое-то действие 2 раза, то вы просто повторяете его:
  
  (display "hello") (display "hello")  
+
  (Вывод "Привет") (Вывод "Привет")  
  
А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>repeat</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий:
+
А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>повторить</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий:
  
 
Если количество повторов больше нуля, то:
 
Если количество повторов больше нуля, то:
1. выполняем действие
 
2. Вызываем функцию repeat с количеством повторов меньшим на 1
 
  
(define (repeat number)
+
1. выполняем необходимое действие
      (if (> number 0) // если количество повторов не нулевое
 
          (begin (display "hello") // выполняем действие
 
                  (repeat (- number 1))))) // повторим действие на единицу меньшее количество раз.
 
  
Попробуем. Запустим один из доступных вам интерпретаторов Scheme, например mzscheme, который можно найти в Интернете по адресу http://www.plt-scheme.org/software/drscheme/ и установить на вашем компьютере.
+
2. из функции Повторить рекурсивно вызываем функцию <code>повторить</code>с количеством повторов меньшим на 1
  
>> (define (repeat number)
+
(функция (повторить ЧислоПовторов)
                (if (> number 0)
+
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
                    (begin (display "hello")
+
          ((вывод "Привет") // выполняем действие
                          (repeat (- number 1)))))
+
                  (повторить(- ЧислоПовторов 1))))) // повторим действие на единицу меньшее количество раз.
> (repeat 0)
+
// проверим функцию Повторить
> (repeat 1)
+
(повторить 5)
hello
 
> (repeat 2)
 
hellohello
 
> (repeat 3)
 
hellohellohello
 
  
 
=== Упражнение 1 ===
 
=== Упражнение 1 ===
  
 
Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз.
 
Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз.
 +
 +
// функция повторить
 +
(функция (повторить ЧислоПовторов ЧтоПовторять)
 +
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
 +
          ((вывод ЧтоПовторять) // выполняем действие
 +
                  (повторить (- ЧислоПовторов 1) ЧтоПовторять ) )) // функция вызывает сама себя с числом повторов на 1 меньше
 +
      ) // повторим действие на единицу меньшее количество раз.
 +
// начало выполнения кода, вызываем функцию Повторить с параметром 5
 +
(повторить 5 1)
  
 
=== Упражнение 2 ===
 
=== Упражнение 2 ===
Строка 237: Строка 325:
 
Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен.
 
Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен.
  
Самое время вспомнить, что в ''Scheme'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>repeat</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает два параметра: первый — количество повторов, второй — функция, которую надо запустить.
+
Самое время вспомнить, что в ''Перфо'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>повторить</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает 2 параметра:
 +
* первый параметр — количество повторов,
 +
* второй параметр — функция, которую надо запустить.
  
  (define (repeat number function)
+
  (функция(повторить ЧислоПовторов ВызываемаяФункция) // описание функции Повторить
  (if (> number 0)
+
(если (> ЧислоПовторов 0)
     (begin (function)
+
     ( (ВызываемаяФункция)
           (repeat (- number 1) function))))  
+
           (повторить (- ЧислоПовторов 1) ВызываемаяФункция))))  
  
Теперь повторять можно разные действия:
+
(функция (Вывести-Один) (вывод "1")) // описание функции Вывести-Один
 +
(функция (Вывести-Привет) (вывод "Привет")) // описание функции Вывести-Привет
 +
// Начало исполнения
 +
(повторить 5 Вывести-Один) // 3 раза выведет на экран "1", используемая функция Вывести-Один
 +
(вывод ПС) // Перевод строки
 +
(повторить 5 Вывести-Привет) // 5 раз выведет на экран "Привет", используемая функция Вывести-Привет
  
(define (print-one) (display "1"))
+
== Принцип наименьших усилий. Анонимные (лямбда)-функции ==
(define (print-hello) (display "hello"))
 
(repeat 3 print-one) // 3 раза выведет на экран "1"
 
(repeat 5 print-hello) // 5 раз выведет на экран "hello"
 
  
== Принцип наименьших усилий ==
+
Не надо делать лишних действий, если их можно избежать. Последний вариант функции '''повторить''' хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию».
  
Не надо делать лишних действий, если их можно избежать. Последний вариант функции repeat хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию».
+
  (функ (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)
 
 
  (lambda (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)
 
  
 
Пример:
 
Пример:
  
  (lambda (x) (* x x))    ; создать функцию, которая вычисляет квадрат числа
+
  (функ (ф) (* ф ф))    // создать функцию, которая вычисляет квадрат числа
  (lambda (x y) (+ x y)) ; создать функцию, которая вычисляет сумму двух чисел
+
  (функ (ф ч) (+ ф ч)) // создать функцию, которая вычисляет сумму двух чисел
  (define square (lambda(x) (* x x)))  ; создать функцию, которая вычисляет квадрат числа и назвать её square.
+
  (перем квадрат (функ (ф) (* ф ф)))  // создать лямбда-функцию с переменой Ф.
 +
// ссылка на функцию присваивается ПЕРЕМЕННОЙ "квадрат".
 +
// При использовании переменной "квадрат" числа 100, это число обрабатывается
 +
// функцией которая была присвоена переменной "квадрат"
 +
// этой переменной можно присвоить другую функцию, и следующее число будет обработано другой функцией
 +
(Вывод "Квадрат  = " (квадрат 100) ПС)
  
 
Оказывается, последняя конструкция то же самое, что и:
 
Оказывается, последняя конструкция то же самое, что и:
  
  (define (square x) (* x x))
+
  (функция (квадрат ф) (* ф ф))
  
 
Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям:
 
Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям:
  
  (repeat 3 (lambda() (display "hello")) ) ; три раза выведем на экран "hello"
+
  (повторить 3 (функ() (вывод "Привет")) ) // три раза выведем на экран "Привет"
  (repeat 5 (lambda() (display "1"))) ; пять раз выведем на экран "1"
+
  (повторить 5 (функ() (вывод "1"))) // пять раз выведем на экран "1"
 +
 
 +
Для <code>повторить</code> используются функции без параметров, поэтому в конструкции <code>функ</code> пустая пара скобок.
 +
 
 +
Анонимные (Лямбда)-функции позволяют передавать и принимать в качестве параметров функции не только числа, переменные строки, но и ссылки на другие функции (а фактически куски кода) которые будут обрабатывать данные внутри вашей функции. При этом при каждом вызове функции в качестве параметра ей можно передавать другую функцию. т.е. менять не только данные которые обрабатывает функция но и часть алгоритма по которому эти данные обрабатываются.
 +
 
 +
Представьте, что у вас есть функция, которая может строить графики и принимает в качестве параметров начало и конец интервала, а так же функцию график которой необходимо построить. Если вы передадите туда функцию Квадрат, то получите график параболы, а если передадите функцию Куб, то получите график гиперболы.
 +
 
 +
Пример:
 +
//создадим две анонимных функции
 +
//и сохраним их в переменные квадрат и куб
 +
(перем квадрат (функ (ф) (* ф ф))) 
 +
(перем куб (функ (ф) (* ф ф ф)))
 +
//создадим функцию для построения графика
 +
(функция (график НачИнтервала КонИнтервала ФункцияГрафика)
 +
    (Для (Инд НачИнтервала КонИнтервала)
 +
      (Вывод "х=" Инд " у=" (ФункцияГрафика Инд) "; ") 
 +
    )
 +
    (Вывод пс)
 +
)
 +
//строим график функции квадрат
 +
(график 1 4 квадрат)
 +
//строим график функции куб
 +
(график -3 3 куб)
 +
 
 +
== Работа с файлами ==
 +
Чтение текста из файла Пример2.perfo  - файл должен существовать в файле с запускаемой программой на Перфо
  
Для <code>repeat</code> используются функции без параметров, поэтому в конструкции <code>lambda</code> пустая пара скобок.
+
//прочитаем текст из файла
 +
(Перем Ф (+ (ФС.ТекущийКаталог) "Пример2.perfo"))
 +
(Вывод "Имя файла: " Ф пс)
 +
(Перем Чт (Новый ЧтениеТекста Ф))
 +
(Перем Т (Чт.ПрочитатьДоКонца))
 +
(Вывод "Содержимое файла: " (СтрЗаменить (СтрЗаменить (Сред Т 100 100) " " "-") (Символы.ВКПС) "-") пс)
  
 
== Списки ==
 
== Списки ==
 +
Из Файла справки [[Перфо - Списки]]
 +
 +
(Список Элемент1 .. ЭлементН) - создание списка
 +
(Длина Список) - количество элементов списка (работает так же для строк и коллекций)
 +
(ПЭЛ Список Индекс) - Получить Значение Элемента (работает так же для строк, массивов и индексируемых коллекций)
 +
(УЭЛ Список Значение Индекс) - Установить Значение Элемента (работает так же для массивов и индексируемых коллекций)
 +
(ОЭЛ Список) - возвращает список '''О'''стальных '''ЭЛ'''ементов кроме первого (работает так же с массивами и индексируемыми коллекциями)
 +
(Лев Список КоличествоЭлементов) - отрезать от списка сЛева указанное количество элементов
 +
(Прав Список КоличествоЭлементов) - отрезать от списка сПрава указанное количество элементов
 +
(Сред Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.
 +
ИЛИ сделать: (Вырез Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.
  
 
Проведём следующую работу:
 
Проведём следующую работу:
  
  (define a 1)
+
  (перем А 1)
  (define b 2)
+
  (перем Б 2)
  (define c 3)
+
  (перем В 3)
  (+ a 1)
+
  (+ А 1)
  (+ b 1)
+
  (+ Б 1)
  (+ c 1)  
+
  (+ В 1)  
  
А как бы нам сразу взять три числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией:
+
А как бы нам сразу взять 3 числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией:
  
  (list <элемент1> <элемент2> <элемент3> …)  
+
  (Список <элемент1> <элемент2> <элемент3> …)  
  
 
Пример:
 
Пример:
  
  (define abc (list 1 1 1))                ; список из трёх единиц
+
  (Перем АБВ (список 1 1 1))                // список АБВ из трёх единиц
  (define lst1 (list 1 2 3))                ; список из трёх разных чисел
+
  (Перем Спис1 (список 1 2 3))                // список Спис1 из трёх разных чисел
  (define lst2 (list "hello" "my" "world")) ; список из строк
+
  (Перем Спис2 (список "Привет" "Мой" "Мир")) // список Спис2 из строк
  (define lst3 (list "hello" 1 "world" 3)) ; список из строк и чисел
+
  (Перем Спис3 (список "Привет" 1 "Мир" 3))   // список Спис3 из строк и чисел
  (define lst4 (list (+ 1 0) 2 3))          ; элементы списка можно вычислять перед его созданием  
+
  (Перем Спис4 (список (+ 1 0) 2 3))          // элементы списка Спис4 можно вычислять перед его созданием  
 +
 +
(вывод (длина Спис4) " элемента в списке " Спис4)
  
Scheme также предоставляет функцию:
+
== ПоКаждому (аналог map в Scheme) ==
 +
Перфо также предоставляет функцию [[ПоВсем]] (map):
  
  (map <функция> <список>)  
+
  (ПоВсем <функция> <список>) -> возвращает <список>
  
 
Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример:
 
Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример:
  
  (define (inc x) (+ x 1))  ; увеличивает число на единицу
+
  (функция (ПлюсОдин ф) (+ ф 1))  // увеличивает число на единицу
  (map inc (list 1 1 1))   ; возвращает список из двоек
+
  (вывод (ПоКаждому ПлюсОдин (список 1 1 1)) )// возвращает список из двоек
  (map square (list 1 2 3)) ; возвращает список из квадратов элементов, то есть 1, 4 и 9  
+
  //(ПоКаждому квадрат (список 1 2 3)) // возвращает список из квадратов элементов, то есть 1, 4 и 9  
  
Вспомним про lambda и решим задачу, которую поставили себе в начале этого раздела:
+
Вспомним про анонимные (лямбда)-функции и решим задачу, которую поставили себе в начале этого раздела:
  
  (define abc (list 1 1 1))
+
  (Перем АБВ (список 1 1 1))
  (map (lambda(x) (+ x 1)) abc)  
+
  (ПоКаждому (функ(Б) (+ Б 1)) АБВ)  
  
 
А можно даже и список не вводить как дополнительную переменную:
 
А можно даже и список не вводить как дополнительную переменную:
  
  (map (lambda(x) (+ x 1))
+
  (ПоКаждому (функ(Б) (+ Б 1))
       (list 1 1 1))  
+
       (список 1 1 1))  
  
 
=== Упражнение 3 ===
 
=== Упражнение 3 ===
  
Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>map</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например
+
Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>ПоКаждому</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например
  
  (print-it 5 (list 1 2 3)) // выведет "(6 7 8)"  
+
  (print-it 5 (список 1 2 3)) // выведет "(6 7 8)"
  
 
== Работа со списками ==
 
== Работа со списками ==
Строка 328: Строка 468:
 
списка? Для этого нам надо только  знать следующие инструкции:
 
списка? Для этого нам надо только  знать следующие инструкции:
  
* <code>(null? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке,
+
* <code>(пустой? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке,
* <code>(car <список>)</code> — возвращает первый элемент списка,
+
* <code>(ПЭЛ <список>)</code> — возвращает первый элемент списка, (car)
* <code>(cdr <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список.
+
* <code>(ОЭЛ <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список (crd).
 +
 
 +
'''Важно'''! Функция <code>Пустой?</code> - не является частью языка Перфо, это обычная функция написана на самом Перфо и помещённая в [[Стандартную Библиотеку Перфо]]. Функция <code>Длина</code> - определена в самом языке Перфо
 +
 
 +
// Функция Пустой?
 +
(функция (Пустой? впСписок) (если (= (Длина впСписок) 0) Да Нет))
 +
(функция (НеПустой? впСписок) (если (= (Длина впСписок) 0) Нет Да))
  
 
Пример:
 
Пример:
  
  (car (list 1 2 3)) ; вернёт 1
+
  (ПЭЛ (Список 1 2 3)) ; вернёт 1
  (cdr (list 1 2 3)) ; вернёт список из 2 и 3, то есть (list 2 3).
+
  (ОЭЛ (список 1 2 3)) ; вернёт список из 2 и 3, то есть (список 2 3)
 +
 
 +
//длина списка
 +
(Вывод "длина списка: " (Длина спис1) пс)  // Вернёт 3
 +
(Вывод "Список пустой? " (Пустой? спис1) пс) // Венёт '''Да'''
  
Проще говоря, функция <code>car</code> возвращает голову списка, а <code>cdr</code> — оставшийся хвост списка.
+
Проще говоря, функция <code>ПЭЛ</code> возвращает голову списка, а <code>ОЭЛ</code> — оставшийся хвост списка.
  
Имя функции <code>print-list</code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий:
+
Имя функции <code>ВыводСписка </code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий:
  
 
Если список не пуст, то:
 
Если список не пуст, то:
  
 
1. выводим голову списка.
 
1. выводим голову списка.
2. вызываем <code>print-list</code> для хвоста списка
+
2. вызываем <code>ВыводСписка</code> для хвоста списка
 
 
(define (print-list lst)
 
(if (not (null? lst))
 
    (begin (display (car lst))
 
          (newline)
 
          (print-list (cdr lst)))))
 
  
Поэкспериментируем в интерпретаторе:
+
(Функция (ВыводСписка внСписок)
>> (print-list (list 1 2 3))
+
          ( если (НеПустой? внСписок)
1
+
            (  (Вывод (ПЭЛ внСписок) (ПС))
2
+
              (ВыводСписка (ОЭЛ внСписок)))
3
+
            )
> (print-list (list "a"))
+
)
a
+
// Проверка работы функции
 +
(ВыводСписка (Список спис2 1 2 3 4))
  
 
=== Упражнение 4 ===
 
=== Упражнение 4 ===
  
Поэкспериментируйте в интерпретаторе с функциями <code>car</code>, <code>cdr</code> и <code>null?</code>.
+
Поэкспериментируйте в интерпретаторе с функциями <code>ПЭЛ</code>, <code>ОЭЛ</code> и <code>пустой?</code>.
  
 
=== Упражнение 5 ===
 
=== Упражнение 5 ===
Строка 370: Строка 515:
 
=== Упражнение 6 ===
 
=== Упражнение 6 ===
  
Пользуясь изученными функциями <code>car</code>, <code>cdr</code> и <code>null?</code> напишите функцию <code>for-each-element</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(for-each-element display (list 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>print-list</code>, пользуясь только что созданной функцией.
+
Пользуясь изученными функциями <code>ПЭЛ</code>, <code>ОЭЛ</code> и <code>пустой?</code> напишите функцию <code>для-каждого-элемента</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(для-каждого-элемента Вывод (Список 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>ВыводСписка </code>, пользуясь только что созданной функцией.
  
 
=== Упражнение 7 ===
 
=== Упражнение 7 ===
  
Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (list 1 2) (list 5 6))</code> должна вернуть список <code>(6 8)</code>.
+
Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (список 1 2) (список 5 6))</code> должна вернуть список <code>(6 8)</code>.
  
 
=== Упражнение 8 ===
 
=== Упражнение 8 ===
  
Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>map</code> — правильный ответ вас сильно удивит.
+
Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>ПоКаждому</code> — правильный ответ вас сильно удивит.
 +
 
 +
== Частичное применение функций (Каррирование) в Перфо ==
 +
 
 +
//изначально существует функция с двумя параметрами
 +
(функция (сумма А Б)
 +
    (+ А Б)
 +
)
 +
//тест
 +
(вывод "(сумма 1 10) = " (сумма 1 10) пс) // возвращает 11
 +
 
 +
//мы хотим получить возможность предварительно задавать один параметр,
 +
//получая при этом новую функцию, примающую только второй параметр
 +
 
 +
(Функция (сумматор А)          // возвращает замкнутое лямбда-выражение, где:
 +
  (Функ (Б)                    // А - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
 +
    (+ А Б))                  // Б - параметр получаемого лямбда-выражения
 +
)                             
 +
 
 +
(Перем плюс1 (сумматор 1))            // делаем функцию для прибавления 1 и сохраняем её в переменную
 +
(вывод "(плюс1 5) = " (плюс1 5) пс)  // возвращает 6
 +
(вывод "(плюс1 10) = " (плюс1 10) пс) // возвращает 11
 +
 
 +
(Перем минус1 (сумматор -1))            // делаем функцию для вычитания 1 и сохраняем её в переменную
 +
(вывод "(минус1 5) = " (минус1 5) пс)  // возвращает 4
 +
(вывод "(минус1 10) = " (минус1 10) пс) // возвращает 9
 +
 
 +
=== Частичное применение функции с тремя параметрами ===
 +
 
 +
//изначально существует функция с тремя параметрами
 +
(функция (СуммаСУмножением А Б К)
 +
    (* (+ А Б) К)
 +
)
 +
//тест
 +
(вывод "(СуммаСУмноженим 2 3 4) = " (СуммаСУмножением 2 3 4) пс) // возвращает 20
 +
 
 +
//мы хотим предварительно задавать коэффициет умножения
 +
 
 +
(Функция (УмножительСуммы К)  // возвращает замкнутое лямбда-выражение, где:
 +
  (Функ (А Б)                  // К - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
 +
    (* (+ А Б) К))            // А и Б - параметры получаемого лямбда-выражения 
 +
)                 
 +
 
 +
(Перем умнож2 (УмножительСуммы 2))            // делаем функцию умножения суммы на 2 и сохраняем её в переменную
 +
(вывод "(умнож2 5 6) = " (умнож2 5 6) пс)    // возвращает 22
 +
(вывод "(умнож2 10 20) = " (умнож2 10 20) пс) // возвращает 60
 +
 
 +
(Перем умнож10 (УмножительСуммы 10))            // делаем функцию умножения суммы на 10 и сохраняем её в переменную
 +
(вывод "(умнож10 5 6) = " (умнож10 5 6) пс)    // возвращает 110
 +
(вывод "(умнож10 10 20) = " (умнож10 10 20) пс) // возвращает 300
 +
 
 +
== [[Препроцессор языка Перфо]] ==
  
 
== См. также ==
 
== См. также ==
Строка 386: Строка 582:
 
* [[w:Лисп|Лисп]], статья в Википедии.
 
* [[w:Лисп|Лисп]], статья в Википедии.
 
* [[w:Scheme|Scheme]], статья в Википедии.
 
* [[w:Scheme|Scheme]], статья в Википедии.
 +
* [http://www.inr.ac.ru/~info21/software.htm примеры задач по Компонентному Паскалю]
 
* [http://www.plt-scheme.org/ Plt Scheme], официальная страница Plt Scheme.
 
* [http://www.plt-scheme.org/ Plt Scheme], официальная страница Plt Scheme.
 
* [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo.
 
* [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo.
 
* [http://www.lispme.de/index.html LispMe], официальная страница LispMe.
 
* [http://www.lispme.de/index.html LispMe], официальная страница LispMe.
* [http://www.iro.umontreal.ca/~gambit/ Gambit-C], здесь можно скачать Gambit-C.
+
* [http://dynamo.iro.umontreal.ca/wiki/index.php/Main_Page Gambit-C], здесь можно скачать Gambit-C.
  
 
[[Категория:Функциональное программирование]]
 
[[Категория:Функциональное программирование]]
 
[[Категория:Перфо]]
 
[[Категория:Перфо]]
 
[[Категория:Scheme]]
 
[[Категория:Scheme]]

Текущая версия на 18:54, 3 декабря 2020

Это заготовка для переделки под язык Перфо - учебника "Введение в Scheme для школьников".
  • Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в журнале «Потенциал».

Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — Лисп. Точнее, один из его самых интересных диалектов — Перфо, который является русским вариантом Scheme (Ским). Перфо не является копией Ским. Это адаптированный для практического программирования вариант интерпретатора функционального языка программирования.

Содержание

Примерное соответствие ключевых слов Ским (Scheme) и Перфо

  •  ; - // - комментарий, многострочный комментарий /* */ - не поддерживается
  • define - перем, функция - (определить)
    • перем - создаёт и изменяет переменную
    • функция - создаёт функцию
    • lambda - функ - создаёт анонимную (лямбда)-функцию
  • read - ввод - ввод строки пользователя
  • display - вывод
  • write - вывод
  • newline - ПС - перевод строки
  • if - если - оператор ветвления ЕСЛИ
  • positive? - (> переменная 0)
  • string-append - - склеивание строк
  • set! -
  • list - список
  • null? - Пустой? - для использования необходимо подключить Стандартную Библиотеку Перфо
  • map - ПоВсем
  • apply - ПоКаждому
  • begin - не к спискам относится, а к последовательности действий... в Перфо можно просто писать последовательность без ключевого слова begin. Например, на Scheme надо было бы написать:
(+ 3 (begin (set! y 4) y)  5)

а на Перфо будет так:

(+ 3 ((перем ф 4) ф) 5)

Перем и Уст с переменными работают похоже, пока дело не касается установки значений полей или свойств объектов, тут уже только Уст подойдет...

Перевод терминов функционального программирования

Введение в синтаксис

Сперва познакомимся с несколько необычным порядком слов этого языка: «действие — предмет». Но необычен он только в сравнении с популярными языками программирования. В русском языке такая последовательность нередка:

  • Сумма трёх и пяти.
  • Произведение пяти, шести и семи.
  • Купи в булочной батон.

Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Перфо:

(+ 3 5)
(* 5 6 7)
(купить булочная батон)

Можно записать выражения и посложнее:

(купить булочная батон (+ 2 1))

«Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше.

Фраза (* 3 5) хороша, а (* ширина высота) — лучше.

Выражение (* 2 3.1415926 5) — интригующе, а (* 2 ЧислоПи Радиус) гораздо более осмысленно.

Здесь ширина, высота — переменные, а 3 и 5 — их текущие значения.

Переменная задаётся следующей конструкцией языка:

(Перем имя <первоначальное значение>)

Пример:

(перем ширина 3)
(перем высота 7)
(* 2 (+ ширина высота)) 

Прочитаем записанное по-человечески: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20.

Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так:

(перем А 3)
(перем Б 4)
(+ (* А А) (* Б Б)) 

Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Перфо — квадрат:

(+ (квадрат А) (квадрат Б)) 

Добавим новые слова в программу

«Сумма квадрата А и квадрата Б». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её квадрат. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом:

(функция (квадрат x) (* x x)) 

Общий формат:

(функция (название параметр параметр …) (тело_функции)) 

Функция возвращает последнее вычисленное значение. Это означает, что следующая функция квадрат2:

(функция (квадрат2 x) (* 2 2) (* x x)) 

вернёт тот же результат, что и квадрат, перед этим умножив 2 на 2 безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново:

(перем А 3)
(перем Б 4)
(функция (квадрат x) (* x x))
(+ (квадрат А) (квадрат Б)) 

Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Перфо, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Перфо до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко.

Сразу пример. Пусть перед нами стоит задача сделать программу, которая спрашивает имя пользователя, а потом выводит ему приветствие.

Перфо предоставляет нам несколько готовых «глаголов»:

  • ввод: для чтения имени,
  • вывод: вывод чего-то на дисплее,
  • ПС: вывод перевода строки.

Мы бы хотели иметь такие «глаголы»:

  • привет: для приветствия с одним параметром — именем пользователя;
  • пользователь: для получения имени пользователя, без параметров.

Наша задача выглядела бы так:

(привет (пользователь)) 

Дело за малым — определить привет и пользователь. Нет проблем. Вот полный текст программы.

(функция (привет имя) // объявление функции Привет с параметром Имя
  (вывод "Привет, ")
  (вывод имя)
  (вывод "!")
  (ПС))
(функция (пользователь) // объявление функции Пользователь без параметров
  (ввод "Представьтесь: "))
(привет (пользователь)) 

Перфо — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется.

Ввод данных в программу

Ввод данных пользователя прост и логичен. Как мы объявляем переменную ?

(Перем ИмяПользователя "Володя")
(Вывод "Вы ввели имя " ИмяПользователя)

Как присвоить новое значение переменной ИмяПользователя ? В Функцию присваивания Перем вместо "Володя" необходимо вставить результат работы другой функции.

(Перем ИмяПользователя (Ввод "Введи своё имя: "))
(Вывод "Вас зовут " ИмяПользователя ПС) // проверим какое  значение присвоилось, напоминаю ПС - перевод строки

Формат функции Ввод

(ввод "Сообщение-подсказка пользователю, что ввести ")

Операция унарный минус

(-) "минус" - Перфо тоже функция/глагол. Если её применить к значению (- 3) или (- ЛюбоеЧисло) то знак поменяется на противоположный. Математическим аналогом является умножение на -1. Операция унарный плюс возможна но не имеет смысла как умножение на +1.

(Вывод "Отрицательное число -3 = " -3 ПС)
(Вывод "Операция унарный минус (- 3) = " (- 3) ПС)
(Вывод "Операция унарный минус (- -3) = " (- -3) ПС)

Ветвление программы - оператор если

Например, функцию «модуль числа» можно определить так:

(функция (модуль ф) // описание функции Модуль с параметром Ф
         (если (> ф 0) // Если ф>0 то выполняется Часть 1 иначе Часть 2
                  ф // Часть 1
               (- ф) // Часть 2
         ) // Конец алгоритма функции Модуль
 ) // Конец описания функции Модуль

«Определим, что функция модуль возвращает свой аргумент, если он положителен, иначе — ». А можно и так:

(функция (модуль ф) // описание функции Модуль с параметром Ф
   // результат выполнения функции - знак (+) или (-) который применяется к переменной Ф 
         ((если (> ф 0) + -) ф) // Если ф>0 то возвращает знак (+) иначе возвращает знак (-)
         // знаки + и - на самом деле функции которые применяются к переменным или числам
         // поэтому результат выполнения функции Модуль - одна из 2-х функций + или - 
         // в  зависимости от значения Ф
)  

«…если аргумент положителен, то плюс, иначе минус ф». Здесь в результате исполнения выражения если возвращается функция + или -, которая затем применяется к аргументу ф. Полагаю, что смысл конструкции если вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков:

(если условие <действие, если условие выполняется> <действие в противном случае>)

Где посмотреть и попробовать

В теории всё хорошо, а где немного попрактиковаться? Необходимо скачать среду языка Перфолента.NET 0.46+, в ней выбрать Файл - "Создать программа на языке Перфо..."

Рекурсия и итерация

Простота Перфо (Лисп, Scheme ) обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии.

Упражнение

Посмотрите следующие две реализации функции вычисления факториала (т.е. умножаются все числа от 1 до N)

f(N) = 1 * 2 * N

Одна из них основана на рекурсии - это когда функция (или кусок кода) вызывает сама себя, а другая – на итерациях - это когда кусок кода повторяется несколько раз в цикле.

Напишите на Перфо рекурсивную (вызывающую саму себя) и основанную на итерациях (повторении) реализации функции возведения в степень N числа А

f(А, N) = A^N.

Вариант 1. Рекурсия - функция вызывает сама себя

(функция (факториал ф) // функция Факториал с параметром Ф
         (если (= ф 0) // если a = 0
                  1 // то функция возвращает в то место в коде откуда её вызвали 1 
               (* ф (факториал (- ф 1) ) ) // умножается значение Ф на значение возвращаемое 
                 // (факториал (- ф 1) ) - с параметром Ф-1 (на единицу меньше)
                 // т.е. внутри функции факториал вызывается функция факториал
                 // с другим (на единицу меньше) параметром!!
    ) 
)

Вариант 2. Повторение (Итерация)

Вычисление факториала методом повторения (итерации):

(функция (факториал-повторением результат счётчик) // определение функции Факториал-повторением 
        (если (= счётчик 0)
              результат
              (факториал-повторением (* счётчик результат)
                                  (- счётчик 1))))
(функция (факториал Число) (факториал-повторением 1 Число)) // описание функции Факториал которая использует функцию факториал-повторением 
// использование функции Факториал
(Вывод "Факториал 6: " (факториал 6)) // вывод " Факториал 6:" +  результат работы функции "Факториал" с параметром 6 
                                      //(которая сама вызывает другую функцию факториал-повторением )

Повторение – мать учения

Приведём небольшую шпаргалку по базовым конструкциям Перфо.

Базовый синтаксис:

(<функция> <аргумент> <аргумент> …) // комментарий 

Например:

(+ 1 2 3 4)                     // сумма первых 4-х натуральных чисел
(+ (+ 1 2) 5 6)                 // возможно вкладывать одни вызовы функций в другие
(+ "Привет" "Мир")              // результат — склеенная строка "ПриветМир" 

Присвоить значение переменной:

(перем <имя> <значение>) 

Объявление Функции:

(функция (<имя> <параметр1> … <параметрН>) <тело функции>)

Функция возвращает значение последнего вычисленного выражения в теле функции

Например:

(перем А 3) // создаётся переменная А = 3
(перем Б 4) // создаётся переменная Б = 4
(функция (квадрат П) (* П П)) // описывается функция которая вычисляет квадрат числа переданного в параметр П
// результат = 16 
(функция (+А Б) (+ Б А)) // да-да, можно давать и такие имена функциям (любое символьное выражение)
// результат = 7

Конструкция Если То Иначе:

(если <условие> <действие при успехе условия> <действие при неудаче>)

Выполнение действий последовательно, как в обычном языке программирования:

(<первое действие> <второе действие> …) 

Пример:

(Перем Б -10) // Переменной Б присваивается 10
(если (> Б 0) // Если Б>0
    (вывод "Б > 0" ПС)) // Б > 0 - ПРАВДА выводится "Б > 0" и переводится строка,  
    // действие Б > 0 - ЛОЖЬ -  можно не указывать
((вывод 1)
 (вывод 2 ПС)
 (вывод 3)) // последовательно выполнятся действия, и на экран будет выведено: 12 и 3
Если вы ранее использовали Scheme то в нём последовательность действий (<первое действие> <второе действие> …) записывается со словом begin (begin <первое действие> <второе действие> …)

И опять про повторение: функция повторить

Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие 1 раз, то вы просто его делаете 1 раз:

(Вывод "Привет") 

Если вам надо выполнить какое-то действие 2 раза, то вы просто повторяете его:

(Вывод "Привет") (Вывод "Привет") 

А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет повторить, и у неё будет один параметр — количество повторов. Алгоритм следующий:

Если количество повторов больше нуля, то:

1. выполняем необходимое действие

2. из функции Повторить рекурсивно вызываем функцию повторитьс количеством повторов меньшим на 1

(функция (повторить ЧислоПовторов)
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
          ((вывод "Привет") // выполняем действие
                 (повторить(- ЧислоПовторов 1))))) // повторим действие на единицу меньшее количество раз. 
// проверим функцию Повторить
(повторить 5)

Упражнение 1

Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз.

// функция повторить
(функция (повторить ЧислоПовторов ЧтоПовторять)
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
          ((вывод ЧтоПовторять) // выполняем действие
                 (повторить (- ЧислоПовторов 1) ЧтоПовторять ) )) // функция вызывает сама себя с числом повторов на 1 меньше
      ) // повторим действие на единицу меньшее количество раз. 
// начало выполнения кода, вызываем функцию Повторить с параметром 5
(повторить 5 1)

Упражнение 2

Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен.

Самое время вспомнить, что в Перфо можно передавать функцию в качестве параметра. Усовершенствуем функцию повторить так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает 2 параметра:

  • первый параметр — количество повторов,
  • второй параметр — функция, которую надо запустить.
(функция(повторить ЧислоПовторов ВызываемаяФункция) // описание функции Повторить
(если (> ЧислоПовторов 0)
   ( (ВызываемаяФункция)
          (повторить (- ЧислоПовторов 1) ВызываемаяФункция)))) 
(функция (Вывести-Один) (вывод "1")) // описание функции Вывести-Один
(функция (Вывести-Привет) (вывод "Привет")) // описание функции Вывести-Привет
// Начало исполнения
(повторить 5 Вывести-Один) // 3 раза выведет на экран "1", используемая функция Вывести-Один
(вывод ПС) // Перевод строки
(повторить 5 Вывести-Привет) // 5 раз выведет на экран "Привет", используемая функция Вывести-Привет

Принцип наименьших усилий. Анонимные (лямбда)-функции

Не надо делать лишних действий, если их можно избежать. Последний вариант функции повторить хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию».

(функ (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)

Пример:

(функ (ф) (* ф ф))    // создать функцию, которая вычисляет квадрат числа
(функ (ф ч) (+ ф ч)) // создать функцию, которая вычисляет сумму двух чисел
(перем квадрат (функ (ф) (* ф ф)))  // создать лямбда-функцию с переменой Ф.
// ссылка на функцию присваивается ПЕРЕМЕННОЙ "квадрат".
// При использовании переменной "квадрат" числа 100, это число обрабатывается 
// функцией которая была присвоена переменной "квадрат"
// этой переменной можно присвоить другую функцию, и следующее число будет обработано другой функцией
(Вывод "Квадрат  = " (квадрат 100) ПС)

Оказывается, последняя конструкция то же самое, что и:

(функция (квадрат ф) (* ф ф))

Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям:

(повторить 3 (функ() (вывод "Привет")) ) // три раза выведем на экран "Привет"
(повторить 5 (функ() (вывод "1"))) // пять раз выведем на экран "1"

Для повторить используются функции без параметров, поэтому в конструкции функ пустая пара скобок.

Анонимные (Лямбда)-функции позволяют передавать и принимать в качестве параметров функции не только числа, переменные строки, но и ссылки на другие функции (а фактически куски кода) которые будут обрабатывать данные внутри вашей функции. При этом при каждом вызове функции в качестве параметра ей можно передавать другую функцию. т.е. менять не только данные которые обрабатывает функция но и часть алгоритма по которому эти данные обрабатываются.

Представьте, что у вас есть функция, которая может строить графики и принимает в качестве параметров начало и конец интервала, а так же функцию график которой необходимо построить. Если вы передадите туда функцию Квадрат, то получите график параболы, а если передадите функцию Куб, то получите график гиперболы.

Пример:

//создадим две анонимных функции 
//и сохраним их в переменные квадрат и куб
(перем квадрат (функ (ф) (* ф ф)))  
(перем куб (функ (ф) (* ф ф ф)))
//создадим функцию для построения графика
(функция (график НачИнтервала КонИнтервала ФункцияГрафика) 
   (Для (Инд НачИнтервала КонИнтервала)
     (Вывод "х=" Инд " у=" (ФункцияГрафика Инд) "; ")  
   ) 
   (Вывод пс)
)
//строим график функции квадрат
(график 1 4 квадрат)
//строим график функции куб
(график -3 3 куб)

Работа с файлами

Чтение текста из файла Пример2.perfo - файл должен существовать в файле с запускаемой программой на Перфо

//прочитаем текст из файла
(Перем Ф (+ (ФС.ТекущийКаталог) "Пример2.perfo"))
(Вывод "Имя файла: " Ф пс)
(Перем Чт (Новый ЧтениеТекста Ф))
(Перем Т (Чт.ПрочитатьДоКонца))
(Вывод "Содержимое файла: " (СтрЗаменить (СтрЗаменить (Сред Т 100 100) " " "-") (Символы.ВКПС) "-") пс)

Списки

Из Файла справки Перфо - Списки

(Список Элемент1 .. ЭлементН) - создание списка
(Длина Список) - количество элементов списка (работает так же для строк и коллекций)
(ПЭЛ Список Индекс) - Получить Значение Элемента (работает так же для строк, массивов и индексируемых коллекций)
(УЭЛ Список Значение Индекс) - Установить Значение Элемента (работает так же для массивов и индексируемых коллекций)
(ОЭЛ Список) - возвращает список Остальных ЭЛементов кроме первого (работает так же с массивами и индексируемыми коллекциями)
(Лев Список КоличествоЭлементов) - отрезать от списка сЛева указанное количество элементов 
(Прав Список КоличествоЭлементов) - отрезать от списка сПрава указанное количество элементов 
(Сред Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.
ИЛИ сделать: (Вырез Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.

Проведём следующую работу:

(перем А 1)
(перем Б 2)
(перем В 3)
(+ А 1)
(+ Б 1)
(+ В 1) 

А как бы нам сразу взять 3 числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией:

(Список <элемент1> <элемент2> <элемент3> …) 

Пример:

(Перем АБВ (список 1 1 1))                 // список АБВ из трёх единиц
(Перем Спис1 (список 1 2 3))                // список Спис1 из трёх разных чисел
(Перем Спис2 (список "Привет" "Мой" "Мир")) // список Спис2 из строк
(Перем Спис3 (список "Привет" 1 "Мир" 3))   // список Спис3 из строк и чисел
(Перем Спис4 (список (+ 1 0) 2 3))          // элементы списка Спис4 можно вычислять перед его созданием 

(вывод (длина Спис4) " элемента в списке " Спис4)

ПоКаждому (аналог map в Scheme)

Перфо также предоставляет функцию ПоВсем (map):

(ПоВсем <функция> <список>) -> возвращает <список>

Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример:

(функция (ПлюсОдин ф) (+ ф 1))  // увеличивает число на единицу
(вывод (ПоКаждому ПлюсОдин (список 1 1 1)) )// возвращает список из двоек
//(ПоКаждому квадрат (список 1 2 3)) // возвращает список из квадратов элементов, то есть 1, 4 и 9 

Вспомним про анонимные (лямбда)-функции и решим задачу, которую поставили себе в начале этого раздела:

(Перем АБВ (список 1 1 1))
(ПоКаждому (функ(Б) (+ Б 1)) АБВ) 

А можно даже и список не вводить как дополнительную переменную:

(ПоКаждому (функ(Б) (+ Б 1))
      (список 1 1 1)) 

Упражнение 3

Пользуясь функциями write (для печати списка на экране) и ПоКаждому, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например

(print-it 5 (список 1 2 3)) // выведет "(6 7 8)"

Работа со списками

А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого списка? Для этого нам надо только знать следующие инструкции:

  • (пустой? <список>) — проверяет, а есть ли ещё какие элементы в списке,
  • (ПЭЛ <список>) — возвращает первый элемент списка, (car)
  • (ОЭЛ <список>) — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список (crd).

Важно! Функция Пустой? - не является частью языка Перфо, это обычная функция написана на самом Перфо и помещённая в Стандартную Библиотеку Перфо. Функция Длина - определена в самом языке Перфо

// Функция Пустой?
(функция (Пустой? впСписок) (если (= (Длина впСписок) 0) Да Нет))
(функция (НеПустой? впСписок) (если (= (Длина впСписок) 0) Нет Да))

Пример:

(ПЭЛ (Список 1 2 3)) ; вернёт 1
(ОЭЛ (список 1 2 3)) ; вернёт список из 2 и 3, то есть (список 2 3)
//длина списка
(Вывод "длина списка: " (Длина спис1) пс)  // Вернёт 3
(Вывод "Список пустой? " (Пустой? спис1) пс) // Венёт Да

Проще говоря, функция ПЭЛ возвращает голову списка, а ОЭЛ — оставшийся хвост списка.

Имя функции ВыводСписка . Единственный параметр — исходный список. Алгоритм работы нашей функции следующий:

Если список не пуст, то:

1. выводим голову списка. 2. вызываем ВыводСписка для хвоста списка

(Функция (ВыводСписка внСписок)
         ( если (НеПустой? внСписок) 
           (  (Вывод (ПЭЛ внСписок) (ПС))
              (ВыводСписка (ОЭЛ внСписок)))
           )
)
// Проверка работы функции
(ВыводСписка (Список спис2 1 2 3 4))

Упражнение 4

Поэкспериментируйте в интерпретаторе с функциями ПЭЛ, ОЭЛ и пустой?.

Упражнение 5

Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль.

Упражнение 6

Пользуясь изученными функциями ПЭЛ, ОЭЛ и пустой? напишите функцию для-каждого-элемента, которая будет применять заданную функцию к каждому элементу данного списка. Например, (для-каждого-элемента Вывод (Список 1 2 3)) должна напечатать «123». Напишите новую версию ВыводСписка , пользуясь только что созданной функцией.

Упражнение 7

Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда (plus (список 1 2) (список 5 6)) должна вернуть список (6 8).

Упражнение 8

Попробуйте решить упражнение 7 с помощью функции ПоКаждому — правильный ответ вас сильно удивит.

Частичное применение функций (Каррирование) в Перфо

//изначально существует функция с двумя параметрами
(функция (сумма А Б)
   (+ А Б)
)
//тест
(вывод "(сумма 1 10) = " (сумма 1 10) пс) // возвращает 11
//мы хотим получить возможность предварительно задавать один параметр,
//получая при этом новую функцию, примающую только второй параметр
(Функция (сумматор А)          // возвращает замкнутое лямбда-выражение, где:
  (Функ (Б)                    // А - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
    (+ А Б))                   // Б - параметр получаемого лямбда-выражения
)                               
(Перем плюс1 (сумматор 1))            // делаем функцию для прибавления 1 и сохраняем её в переменную
(вывод "(плюс1 5) = " (плюс1 5) пс)   // возвращает 6
(вывод "(плюс1 10) = " (плюс1 10) пс) // возвращает 11
(Перем минус1 (сумматор -1))            // делаем функцию для вычитания 1 и сохраняем её в переменную
(вывод "(минус1 5) = " (минус1 5) пс)   // возвращает 4
(вывод "(минус1 10) = " (минус1 10) пс) // возвращает 9

Частичное применение функции с тремя параметрами

//изначально существует функция с тремя параметрами
(функция (СуммаСУмножением А Б К)
   (* (+ А Б) К)
)
//тест
(вывод "(СуммаСУмноженим 2 3 4) = " (СуммаСУмножением 2 3 4) пс) // возвращает 20
//мы хотим предварительно задавать коэффициет умножения
(Функция (УмножительСуммы К)   // возвращает замкнутое лямбда-выражение, где:
 (Функ (А Б)                  // К - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
   (* (+ А Б) К))             // А и Б - параметры получаемого лямбда-выражения  
)                   
(Перем умнож2 (УмножительСуммы 2))            // делаем функцию умножения суммы на 2 и сохраняем её в переменную
(вывод "(умнож2 5 6) = " (умнож2 5 6) пс)     // возвращает 22
(вывод "(умнож2 10 20) = " (умнож2 10 20) пс) // возвращает 60
(Перем умнож10 (УмножительСуммы 10))            // делаем функцию умножения суммы на 10 и сохраняем её в переменную
(вывод "(умнож10 5 6) = " (умнож10 5 6) пс)     // возвращает 110
(вывод "(умнож10 10 20) = " (умнож10 10 20) пс) // возвращает 300

Препроцессор языка Перфо

См. также